package ca.uqac.dim.turtledb.querytranslator;

import ca.uqac.dim.turtledb.*;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * .
 * User: damien
 * Date: 12-11-24
 * Time: 21:20
 */
class ConditionStatementParser {


    public static final String SQL_WHERE_STATEMENT_KEYWORD_AND = "AND";
    public static final String SQL_WHERE_STATEMENT_KEYWORD_OR = "OR";
    private List<String> cleaned = new ArrayList<String>();
    private Condition currentLeft = null;
    private List<String> rawSplittedStatement;

    public ConditionStatementParser(List<String> splittedWhereStatement) {
        rawSplittedStatement = splittedWhereStatement;
    }

    public Condition process() {
        if(rawSplittedStatement == null || rawSplittedStatement.isEmpty()) {
            return null; //Petit raccourci :)
        }
        for (String s : rawSplittedStatement) {
            //On nettoie d'abord vis à vis des ( et des )
            cleaned.addAll(clean(s));
        }
        Iteration iteration = new Iteration(currentLeft, cleaned);
        currentLeft = iteration.process();
        return currentLeft;
    }


    private List<String> clean(String s) {
        List<String> returnValue = new LinkedList<String>();
        int i;
        if ((i = s.indexOf("(")) != -1) {
            returnValue.addAll(clean(s.substring(0, i)));
            returnValue.add("(");
            returnValue.addAll(clean(s.substring(i + 1)));
        } else if ((i = s.indexOf(")")) != -1) {
            returnValue.addAll(clean(s.substring(0, i)));
            returnValue.add(")");
            returnValue.addAll(clean(s.substring(i + 1)));
        } else {
            if (!s.isEmpty())
                returnValue.add(s);
        }
        return returnValue;
    }

    public static void test() {
        System.out.println("ConditionStatementParser TESTS");
        System.out.println("ConditionStatementParser Preliminary TESTS");
        Iteration.test();

    }

    private static class Iteration {


        public static void test() {
            System.out.println("ConditionStatementParser.Iteration  TESTS");
            System.out.println("ConditionStatementParser.Iteration Preliminary TESTS");
            boolean unaryCondition_test_result = getUnaryCondition_test();

            System.out.println("END OF ConditionStatementParser.Iteration Preliminary TESTS");
            if (unaryCondition_test_result) {
                System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet");

                try {
                    Condition result = new Iteration(null, new ArrayList<String>(Arrays.asList(new String[]{"Pouet", "=", "pouet"}))).process();

                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "SUCCESS with result :");
                    System.out.println(result);
                } catch (Exception e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "FAILED With Exception :");
                    System.out.println(e);
                }

                System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet AND Pouet = Pouet ");
                try {
                    Condition result = new Iteration(null, new ArrayList<String>(Arrays.asList(new String[]{"Pouet", "=", "pouet", "AND", "Pouet", "=", "pouet"}))).process();

                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet" + "SUCCESS with result :");
                    System.out.println(result);
                } catch (Exception e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "FAILED With Exception :");
                    System.out.println(e);
                }

                System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet AND Pouet = Pouet AND Pouet = Pouet ");
                try {
                    Condition result = new Iteration(null, new ArrayList<String>(Arrays.asList(new String[]{"Pouet", "=", "pouet", "AND", "Pouet", "=", "pouet", "AND", "Pouet", "=", "pouet"}))).process();

                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet" + "SUCCESS with result :");
                    System.out.println(result);
                } catch (Exception e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "FAILED With Exception :");
                    System.out.println(e);
                }


                System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet AND (Pouet = Pouet AND Pouet = Pouet)");
                try {
                    Condition result = new Iteration(null, new ArrayList<String>(Arrays.asList(new String[]{"Pouet", "=", "pouet", "AND", "(", "Pouet", "=", "pouet", "AND", "Pouet", "=", "pouet", ")"}))).process();

                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "SUCCESS with result :");
                    System.out.println(result);
                } catch (Exception e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "FAILED With Exception :");
                    System.out.println(e);
                }

                System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet AND (Pouet = Pouet OR Pouet = Pouet) ");
                try {
                    Condition result = new Iteration(null, new ArrayList<String>(Arrays.asList(new String[]{"Pouet", "=", "pouet", "AND", "(", "Pouet", "=", "pouet", "OR", "Pouet", "=", "pouet", ")"}))).process();

                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "SUCCESS with result :");
                    System.out.println(result);
                } catch (Exception e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "FAILED With Exception :");
                    System.out.println(e);
                }

                System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet AND (Pouet = Pouet OR Pouet = Pouet) ");
                try {
                    Condition result = new Iteration(null, new ArrayList<String>(Arrays.asList(new String[]{"Pouet", "=", "pouet", "AND", "(", "Pouet", "=", "pouet", "OR", "Pouet", "=", "pouet", ")"}))).process();

                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "SUCCESS with result :");
                    System.out.println(result);
                } catch (Exception e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "FAILED With Exception :");
                    System.out.println(e);
                }


                System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet AND (Pouet = Pouet OR Pouet = Pouet) OR Pouet = POUET ");
                try {
                    Condition result = new Iteration(null, new ArrayList<String>(Arrays.asList(new String[]{"Pouet", "=", "pouet", "AND", "(", "Pouet", "=", "pouet", "OR", "Pouet", "=", "pouet", ")", "OR", "Pouet", "=", "POUET"}))).process();

                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "SUCCESS with result :");
                    System.out.println(result);
                } catch (Exception e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "FAILED With Exception :");
                    System.out.println(e);
                }

                System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet AND (Pouet = Pouet OR Pouet = Pouet OR Pouet = Pouet) OR Pouet = POUET ");
                try {
                    Condition result = new Iteration(null, new ArrayList<String>(Arrays.asList(new String[]{"Pouet", "=", "pouet", "AND", "(", "Pouet", "=", "pouet", "OR", "Pouet", "=", "pouet", "OR", "Pouet", "=", "pouet", ")", "OR", "Pouet", "=", "POUET"}))).process();

                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "SUCCESS with result :");
                    System.out.println(result);
                } catch (Exception e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    System.out.println("ConditionStatementParser.Iteration  TESTS " + " " + " Pouet = Pouet " + "FAILED With Exception :");
                    System.out.println(e);
                }

            }

        }

        private Condition iterationLeft;
        private List<String> iterationProcessingSplittedStatement;

        public Iteration(Condition iterationLeft, List<String> iterationProcessingSplittedStatement) {
            this.iterationLeft = iterationLeft;
            this.iterationProcessingSplittedStatement = iterationProcessingSplittedStatement;
        }

        private Condition process() {
            //Verifier si on est pas dans un cas avec parenthese forme ( )
            if (iterationProcessingSplittedStatement.get(0).equals("(")) {
                int compteurParenthese = 1;
                int i = 1;        //Le 0 c'est une ( on le sait !
                while (compteurParenthese >= 1 && i < iterationProcessingSplittedStatement.size()) {
                    if (iterationProcessingSplittedStatement.get(i).equals("(")) compteurParenthese++;
                    if (iterationProcessingSplittedStatement.get(i).equals(")")) compteurParenthese--;
                    i++;
                }

                int finDuPremierOperande = i;
                //Si l'on est sorti de la boucle a cause d'une parenthèse fermée, alors le curseur est allé trop loin;
                if (finDuPremierOperande >= iterationProcessingSplittedStatement.size() || !iterationProcessingSplittedStatement.get(finDuPremierOperande).equals(")")) {
                    finDuPremierOperande--;
                }
                //Verifier que la parenthèse est bien fermée
                if (compteurParenthese != 0) {
                    throw new NotImplementedException();
                }
                //Cas Particulier : y'a rien après
                if (i >= iterationProcessingSplittedStatement.size()) {
                    //Alors c'est une fin de recursion et on enleve les parenthèses et on itere
                    Condition condition = new Iteration(null, iterationProcessingSplittedStatement.subList(1, finDuPremierOperande)).process();
                    return condition;

                }
                //Cas normal : Y'a un operateur après
                else if (compteurParenthese == 0
                        && i < iterationProcessingSplittedStatement.size()
                        && (
                        iterationProcessingSplittedStatement.get(i).equalsIgnoreCase(SQL_WHERE_STATEMENT_KEYWORD_AND)
                                || iterationProcessingSplittedStatement.get(i).equalsIgnoreCase(SQL_WHERE_STATEMENT_KEYWORD_OR))
                        ) {
                    return recursionAlternate(i, finDuPremierOperande);
                }
                //Cas non géré : Y'a un truc bizzare après
                else {
                    throw new NotImplementedException();
                }

            }
            //Cas sans parenthèse
            else {

                //On va chercher le premier operande binaire
                int indexOfFirstBinaryCondition = findIndexOfFirstBinaryCondition();


                if (indexOfFirstBinaryCondition == iterationProcessingSplittedStatement.size()) //Cas de fin de recursion, il n'y a pas de binaire
                {
                    //Du coup on cherche le unaire
                    return getUnaryCondition(iterationProcessingSplittedStatement);
                } else {
                    NAryCondition condition = null;
                    //On suppose que la requete est valide (pas d'opérandes vides...)
                    //On va chercher la condition à gauche, s'il n'y en a pas encore
                    if (iterationLeft == null) {
                        iterationLeft = getUnaryCondition(iterationProcessingSplittedStatement.subList(0, indexOfFirstBinaryCondition));
                    }
                    condition = findConditionType(indexOfFirstBinaryCondition);
                    recursion(indexOfFirstBinaryCondition, condition);

                    return condition;

                }
            }
        }

        private Condition recursionAlternate(int i, int finDuPremierOperande) {
            NAryCondition condition = findConditionType(i);
            condition.addCondition(new Iteration(null, iterationProcessingSplittedStatement.subList(1, finDuPremierOperande)).process());
            Iteration iteration = new Iteration(null, iterationProcessingSplittedStatement.subList(finDuPremierOperande + 2, iterationProcessingSplittedStatement.size()));
            condition.addCondition(iteration.process());
            return condition;
        }

        private void recursion(int indexOfFirstBinaryCondition, NAryCondition condition) {
            condition.addCondition(iterationLeft);
            Iteration nextIteration = new Iteration(null,
                    iterationProcessingSplittedStatement
                            .subList(
                                    indexOfFirstBinaryCondition + 1,
                                    iterationProcessingSplittedStatement.size()
                            )
            );
            condition.addCondition(
                    nextIteration.process()
            );

        }

        private NAryCondition findConditionType(int indexOfFirstBinaryCondition) {
            NAryCondition condition;//On regarde le type d'operateur
            if (iterationProcessingSplittedStatement.get(indexOfFirstBinaryCondition).equalsIgnoreCase(SQL_WHERE_STATEMENT_KEYWORD_AND)) {
                condition = new LogicalAnd();
            } else if (iterationProcessingSplittedStatement.get(indexOfFirstBinaryCondition).equalsIgnoreCase(SQL_WHERE_STATEMENT_KEYWORD_OR)) {
                condition = new LogicalOr();
            } else {
                throw new NotImplementedException();
            }
            return condition;
        }

        private int findIndexOfFirstBinaryCondition() {
            int indexOfFirstBinaryCondition = 0;
            while (indexOfFirstBinaryCondition < iterationProcessingSplittedStatement.size()
                    && !iterationProcessingSplittedStatement.get(indexOfFirstBinaryCondition).equalsIgnoreCase(SQL_WHERE_STATEMENT_KEYWORD_AND)
                    && !iterationProcessingSplittedStatement.get(indexOfFirstBinaryCondition).equalsIgnoreCase(SQL_WHERE_STATEMENT_KEYWORD_OR)
                    ) {
                indexOfFirstBinaryCondition++;
            }
            return indexOfFirstBinaryCondition;
        }

        private Equality getUnaryCondition(List<String> proccessingSplittedStatement) {
            Attribute attribute = new Attribute(proccessingSplittedStatement.get(0));
            Value value = new Value(proccessingSplittedStatement.get(2));
            return new Equality(attribute, value);
        }

        private static boolean getUnaryCondition_test() {
            System.out.println("Test de getUnaryCondition");
            try {
                Iteration iteration = new Iteration(null, new ArrayList<String>(Arrays.asList(new String[]{"Pouet", "=", "pouet"})));
                iteration.getUnaryCondition(iteration.iterationProcessingSplittedStatement);
                Equality unaryCondition = iteration.getUnaryCondition(iteration.iterationProcessingSplittedStatement);
                System.out.println("Test de getUnaryCondition " + "SUCCESS !");
                System.out.println(unaryCondition.toString());
                return true;

            } catch (Exception e) {
                System.out.println("Test de getUnaryCondition " + "FAILED with Exception : ");
                System.out.println(e);
                return false;
            }
        }
    }
}
